#include <curl/curl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include "multiDownload.h"



struct fileInfo** pInfoTable; // 全局变量，指向结构体指针数组

// 回调函数，负责将数据写入文件
size_t writeFunc(void* ptr, size_t size, size_t memb, void* userdata) {
    struct fileInfo* info = (struct fileInfo*)userdata;
    memcpy(info->fileptr + info->offset, ptr, size*memb); 
    info->offset+=size*memb;

    return size*memb;
}

// 回调函数，负责消除curl_easy_setopt(curl, CURLOPT_HEADER, 1);将数据打印到控制台的行为
size_t writeNoFunc(void* ptr, size_t size, size_t memb, void* userdata) {

    return size*memb;
}

// 下载进度会传入该函数处理
int progressFunc(void* userdata, double totalDownload, double nowDownload, double totalUpload, double nowUpload)
{  // 每个线程都会单独调用progressFunc，并且获取到的nowDownload和totalDownload也是当前线程下载的文件块的下载信息
    int percent = 0;
    static int print = 0;
    struct fileInfo* info = (struct fileInfo*)userdata;
    info->nowDownload = nowDownload; //更新当前线程操作的结构体的download
    info->totalDownload = totalDownload;

    if(totalDownload>0){
        int i=0;
        double allNowDownload = 0;
        double allTotalDownload = 0;
        for(i=0; i<=THREAD_NUM; i++){//将所有线程，也就是info[0-10]的download加在一起
            allNowDownload+=pInfoTable[i]->nowDownload;
            allTotalDownload+=pInfoTable[i]->totalDownload;
        }

        percent = (int)(allNowDownload/allTotalDownload*100);
    }
    if(percent>print){
        printf("percent: %d%%\n", percent);  // 由于一些原因，percent总体上趋于100，但是局部会有下降，因此这样写确保它是一直上升的
        print=percent;
    }


    return 0;
}

// 获取下载文件的大小
double getDownloadFileLength(const char* url) {
    double downloadFileLength = 0;  // 创建变量记录文件长度，文件长度是个复数
    CURL* curl = curl_easy_init();

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36");
    curl_easy_setopt(curl, CURLOPT_HEADER, 1);
    curl_easy_setopt(curl, CURLOPT_NOBODY,1);  // 只获取响应头，不包含响应体
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeNoFunc);  

    CURLcode res =  curl_easy_perform(curl);
    if(res == CURLE_OK) {
        curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &downloadFileLength);
    }else {
        perror("downloadFileLength error\n");
        downloadFileLength = -1;
    }
    curl_easy_cleanup(curl);

    return downloadFileLength;
}

// 线程函数
void* worker(void* arg){
    struct fileInfo* info = (struct fileInfo*)arg;

    char range[64] = {0};
    if(info->recordFile){
        fscanf(info->recordFile, "%d-%d", &info->offset, &info->end);  
    }
    if(info->offset > info->end) return NULL;  // 当一段文件被下载完时，offset = end+1，因此作判断返回
    snprintf(range, 64, "%d-%d", info->offset, info->end);  //设置range字符数组的值
    
    printf("threadId: %ld, download from %d to %d\n", info->thid, info->offset, info->end);
    // curl
    CURL* curl = curl_easy_init();

    curl_easy_setopt(curl, CURLOPT_URL, info->url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc); 
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, info);  // 将info 传入writeFunc进行操作

    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
    curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progressFunc);
    curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, info);

    curl_easy_setopt(curl, CURLOPT_RANGE, range);  // 根据range字符串指定获取的数据范围
    CURLcode res =  curl_easy_perform(curl);
    if(res != CURLE_OK) {
        printf("res %d\n", res);
    }
    curl_easy_cleanup(curl);  // 清理curl

    return NULL;
}

// 下载
int download(const char* url, const char* filename) {
    
    long fileLength = getDownloadFileLength(url);
    printf("fileLength: %ld\n", fileLength);
    // write
    int fd = open(filename, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR );  
    if(fd == -1) {
        return -1;
    }

    if(-1 == lseek(fd, fileLength-1, SEEK_SET))  // 开辟空间
    {
        perror("lseek");
        close(fd);
        return -1;
    }

    if(1!=write(fd, "", 1)){  
        perror("write");
        close(fd);
        return -1;
    }

    char* fileptr = (char*)mmap(NULL, fileLength, PROT_READ|PROT_WRITE, MAP_SHARED,fd, 0); 
    if(fileptr == MAP_FAILED){  
        perror("mmap");
        close(fd);
        return -1;
    }

    FILE* fp = fopen("a.txt", "r");  // 打开记录文件传输断点的文件

    // 多线程
    int i=0;
    long partSize = fileLength / THREAD_NUM;  // 分块
    struct fileInfo* info[THREAD_NUM+1] = {NULL};  // 创建结构体指针数组
    
    for(i=0; i<=THREAD_NUM; i++){  // 给每个结构体指针分配内存，并设置其中的值
        info[i] = (struct fileInfo*)malloc(sizeof(struct fileInfo));
        memset(info[i], 0, sizeof(struct fileInfo));  // 将info[i]指向的结构体中的所有值置为0
        info[i]->offset = i*partSize;
        if(i<THREAD_NUM){
            info[i]->end = (i+1)*partSize - 1;
        }else{
            info[i]->end = fileLength-1;
        }
        info[i]->fileptr = fileptr;
        info[i]->url = url;
        info[i]->recordFile = fp;
    }
    pInfoTable = info;
    // 线程创建
    for(i=0; i<=THREAD_NUM; i++){
        pthread_create(&(info[i]->thid), NULL, worker, info[i]);
        usleep(1);
    }

    // 线程启动
    for(i=0; i<=THREAD_NUM; i++){
        pthread_join(info[i]->thid, NULL);
    }
    
    // 释放结构体指针指向的内存
    for(i=0; i<=THREAD_NUM; i++){
        free(info[i]);  
    }
    if(fp){  // 验证a.txt是否存在
        fclose(fp);
        if(remove("a.txt") !=0){  // 文件正常下载完成且a.txt存在才会执行到这里，删除a.txt
            perror("error delete a.txt!");
        }
    }
    close(fd); 
    munmap(fileptr, fileLength);  
    

    return 0;
}

// 信号处理函数
void signal_handler(int signum){
    printf("signum: %d\n", signum);
    int i=0;
    int fd = open("a.txt", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
    if(fd==-1)
    {
        exit(1);
    }
    for(i=0; i<=THREAD_NUM; i++)
    {  // 把断开下载时，每个线程的下载情况写入a.txt
        char range[64] = {0};
        snprintf(range, 64, "%d-%d\r\n", pInfoTable[i]->offset, pInfoTable[i]->end); 

        write(fd, range, strlen(range));
    }
    close(fd);
    exit(1);
}

// 根据URL创建文件名
void getFileName(const char* url, char** filename){
    const char* lastSlash = strrchr(url, '/');
    if(lastSlash!=NULL){
        *filename = (char*)malloc(strlen(lastSlash+1)+3);
        strcpy(*filename, ++lastSlash);
    }
    printf("%s\n", lastSlash);
    size_t len = strlen(*filename);
    (*filename)[len-4] = 'c';
    (*filename)[len-3] = 'p';
    (*filename)[len-2] = 'y';
    (*filename)[len-1] = lastSlash[len-4];
    (*filename)[len] = lastSlash[len-3];
    (*filename)[len+1] = lastSlash[len-2];
    (*filename)[len+2] = lastSlash[len-1];
    (*filename)[len+3] = '\0';
}

// 传入url，调用下载
int url2download(const char* url)
{
    char* filename;
    getFileName(url, &filename);
    if(SIG_ERR == signal(SIGINT, signal_handler))  // 注册信号处理函数
    {
        perror("signal\n");
        return -1;
    }
    download(url, filename);

    return 0;
}


int main(int argc, char* argv[]) {

    // "https://uu.gdl.netease.com/5051/UU-5.30.1.exe"
    if(argc<2)
    {
        printf("lack url argument\n");
        exit(1);
    }
    url2download(argv[1]);    
    return 0;
}